﻿using Microsoft.Phone.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Navigation;

namespace SMARTMobile.WinPhone
{
    public class PageNavigationService : IApplicationService
    {
        private static readonly object SyncRoot = new object();
        private readonly AutoResetEvent _handleEvent = new AutoResetEvent(false);
        private readonly Queue<NavigationHandler> _handlers = new Queue<NavigationHandler>();

        private volatile bool _shutdown;
        private PageNavigationConfiguration _configuration;
        private Thread _workerThread;
        
        public PageNavigationService(SynchronizationContext current)
        {
            SynchronizationContext = current;
        }

        public static PageNavigationService Current { get; private set; }

        public SynchronizationContext SynchronizationContext { get; private set; }

        public void StartService(ApplicationServiceContext context)
        {
            Debug.WriteLine("PageNavigationService::StartService()");

            Debug.Assert(null == Current);
            _configuration = PhoneConfiguration.Current.NavigationMap;
            Current = this;
            _workerThread = new Thread(() =>
            {
                Debug.WriteLine("PageNavigationService thread started");

                while (!_shutdown)
                {
                    _handleEvent.WaitOne();

                    lock (SyncRoot)
                    {
                        int count = _handlers.Count;
                        while (count-- > 0)
                        {
                            var wrapper = _handlers.Dequeue();
                            wrapper.Execute();
                        }
                    }
                }
                Debug.WriteLine("PageNavigationService thread shutting down");
            });
            _workerThread.Start();
        }

        public void StopService()
        {
            Debug.WriteLine("PageNavigationService::StopService()");
            _shutdown = true;
            _handleEvent.Set();
            _workerThread.Join(3000);
        }

        public void Load()
        {
            Frame.Navigated += FrameNavigated;
            Frame.NavigationFailed += NavigationFailed;
        }

        public void Unload()
        {
            Frame.Navigated -= FrameNavigated;
            Frame.NavigationFailed -= NavigationFailed;
        }

        public void Pop()
        {
            if (Frame.CanGoBack)
            {
                Frame.RemoveBackEntry();
            }
        }

        public void GoBack()
        {
            // HACK: launching the Browser task will cause this to fail
            // under certain situations...so give it a few tries...
            int retry = 0;
            while (retry++ < 3)
            {
                try
                {
                    if (Frame.CanGoBack)
                    {
                        Frame.GoBack();
                    }
                    break;
                }
                catch (InvalidOperationException e)
                {
                }
                catch (Exception)
                {
                    // eat it
#if DEBUG
                    MessageBox.Show("PageNavigationService::GoBack() exception");
#endif
                }
            }
        }

        public void Navigate(string pagename, int popCount, params string[] args)
        {
            PageNavigationConfiguration.PageMapping mapping = _configuration.FindPage(pagename);
            Debug.Assert(null != mapping);
            Debug.WriteLine(mapping);
            NavigationHandler item = new NavigationHandler(mapping, args, popCount);
            lock (SyncRoot)
            {
                _handlers.Enqueue(item);
            }
            _handleEvent.Set();
        }

        public void Navigate(string pagename, params string[] args)
        {
            PageNavigationConfiguration.PageMapping mapping = _configuration.FindPage(pagename);
            Debug.Assert(null != mapping);
            Debug.WriteLine(mapping);
            NavigationHandler item = new NavigationHandler(mapping, args);
            lock (SyncRoot)
            {
                _handlers.Enqueue(item);
            }
            _handleEvent.Set();
        }

        private void NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            // TODO: Logger, Post exception to UI
            Debug.WriteLine(e.Exception.Message);
            e.Handled = true;
        }

        private void FrameNavigated(object sender, NavigationEventArgs e)
        {
            //var page = e.Content as MobiPhonePage;
            //if (null == page)
            //{
            //    Logger.Current.Warning("FrameNavigated: base page is not MobiPhonePage");
            //}
        }

        private PhoneApplicationFrame Frame
        {
            get
            {
                return Application.Current.RootVisual as PhoneApplicationFrame;
            }
        }

        internal class NavigationHandler
        {
            private readonly PageNavigationConfiguration.PageMapping _mapping;
            private readonly string[] _args;
            private readonly int _popCount;

            public NavigationHandler(PageNavigationConfiguration.PageMapping mapping, string[] args, int popCount)
                : this(mapping, args)
            {
                this._popCount = popCount;
            }

            public NavigationHandler(PageNavigationConfiguration.PageMapping mapping, string[] args)
            {
                this._mapping = mapping;
                this._args = args;
            }

            public void Execute()
            {
                Uri uri = this._mapping.GetUri(this._popCount, this._args);
                Current.SynchronizationContext.Post(OnExecute, uri);
            }

            private void OnExecute(object state)
            {
                Uri uri = state as Uri;
                Current.Frame.Navigate(uri);
            }
        }
    }
}
